/***************************************************************************
 *   Copyright (C) 2006 by Robert R. Enderlein                             *
 *   robert_r_e@gmx.ch                                                     *
 *                                                                         *
 *   Modified and extended for Omega VII by Senary                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <sstream>
#include <windows.h> //required by adll.h
#include <string>
#include "sdk_engine/adll.h"

#include "Vector.h"
#include "Angle.h"
using namespace std;


// Creates a unit vector from an angle
// equivalent to vec_for_angle() in GameStudio
Vector::Vector(Angle const& rhs)
{
	double ct = cos(rhs.tilt);
	x = cos(rhs.pan)*ct;
	y = sin(rhs.pan)*ct;
	z = sin(rhs.tilt);
}


/// Set a vector from it's GameStudio equivalent
// Param rhs: the address of an VECTOR struct or, equivalently, the
//            address of the x attribute, converted to VECTOR*
Vector::Vector(VECTOR const* rhs)
{
	x = _FLOAT(rhs->x);
	y = _FLOAT(rhs->y);
	z = _FLOAT(rhs->z);
}


/// returns a random unit vector
Vector Vector::randomVector()
{
	// This is not quite distributed uniformly, but should do pretty well...
	double x = (double)rand() / RAND_MAX - 0.5;
	double y = (double)rand() / RAND_MAX - 0.5;
	double z = (double)rand() / RAND_MAX - 0.5;

	Vector res(x, y, z);
	res /= res.r();

	return res;
}



///Set vector by cylindrical coordinates
void Vector::cylindrical(double r, double phi, double h)
{
	x = r*cos(phi);
	y = r*sin(phi);
	z = h;
}


///Set vector by spheric coordinates
void Vector::spherical(double r, double theta, double phi)
{
	x = r*sin(theta)*cos(phi);
	y = r*sin(theta)*sin(phi);
	z = r*cos(theta);
}


///Get the radius squared of the vector
double Vector::r2() const
{
	return x*x + y*y + z*z;
}


///Get the radius of the vector
double Vector::r() const
{
	return sqrt(r2());
}


///Get the phi angle (cylindrical & spheric)
double Vector::phi() const
{
	return atan2(y, x);
}


///Get the theta angle (spheric)
double Vector::theta() const
{			
	return atan2(sqrt(x*x + y*y), z);
}


///Assign operator
Vector& Vector::operator=(Vector const& rhs)
{
	x = rhs.x;
	y = rhs.y;
	z = rhs.z;
	return *this;
}


///Add to the current vector
Vector& Vector::operator+=(Vector const& rhs)
{
	x+=rhs.x;
	y+=rhs.y;
	z+=rhs.z;
	return *this;
}


///Remove from the current vector
Vector& Vector::operator-=(Vector const& rhs)
{
	x-=rhs.x;
	y-=rhs.y;
	z-=rhs.z;
	return *this;
}


///Multiply with a scalar
Vector Vector::operator*(double const& k) const
{
	return Vector(x*k, y*k, z*k);
}


///Multiply with a scalar
Vector& Vector::operator*=(double const& k)
{
	x*=k;
	y*=k;
	z*=k;
	return *this;
}


///Divide by a scalar
Vector Vector::operator/(double const& k) const
{
	return Vector(x/k, y/k, z/k);
}


///Divide this vector by a scalar
Vector& Vector::operator/=(double const& k)
{
	x/=k;
	y/=k;
	z/=k;
	return *this;
}


///Unary minus
Vector Vector::operator-() const
{
	Vector res(-x, -y, -z);
	return res;
}



///Projection
Vector Vector::getProj(Vector const& rhs) const
{
	Vector res(rhs);

	res*=(x*rhs.x + y*rhs.y + z*rhs.z);
	res/=(rhs.x*rhs.x + rhs.y*rhs.y + z*rhs.z);

	return res;
}


///Project this vector on another one
Vector& Vector::proj(Vector const& rhs)
{
	Vector res(rhs);

	double k;

	k=(x*rhs.x + y*rhs.y + z*rhs.z)/(rhs.x*rhs.x + rhs.y*rhs.y + rhs.z*rhs.z);

	x=k*rhs.x;
	y=k*rhs.y;
	z=k*rhs.z;

	return *this;
}


///Rotate this vector around "axis" by the amount "angle"
Vector& Vector::rotate(Vector axis, double angle)
{
	double s = sin(angle);
	double c = cos(angle);
	double t = 1 - c;

	axis /= axis.r();
	
	Vector res;
	res.x = x * (t * axis.x * axis.x + c) +
		    y * (t * axis.x * axis.y - s * axis.z) +
			z * (t * axis.x * axis.z + s * axis.y);
	res.y = x * (t * axis.x * axis.y + s * axis.z) +
		    y * (t * axis.y * axis.y + c) +
			z * (t * axis.y * axis.z - s * axis.x);
	res.z = x * (t * axis.x * axis.z - s * axis.y) +
		    y * (t * axis.y * axis.z + s * axis.x) +
			z * (t * axis.z * axis.z + c);

	*this = res;
	return *this;
}

///Rotate this vector towards "dest", by at most "angle".
Vector& Vector::rotateTowards(Vector const& dest, double angle)
{
	double a = angleBetween(*this, dest);
	if (a < angle) {
		*this = dest * (r() / dest.r());
		return *this;
	}
	
	// first find a normal to the rotation plane
	Vector normal = cross(*this, dest);
	double r = normal.r();
	if (r < 0.001) {
		// vectors are colinear... in this case rotate about z axis
		normal = Vector(0.0, 0.0, 1.0);
	} else {
		normal /= r;
	}

	return rotate(normal, angle);
}




/// Return a string representation of the vector
std::string Vector::toString() const
{
	std::ostringstream oss;
	oss << '(' << x << " , " << y << " , " << z << ')' << std::ends;
	return oss.str();
}

// Converts a Vector back onto the supplied GameStudio VECTOR pointer
// Param vec: the address of an VECTOR struct or, equivalently, the
//            address of the x attribute, converted to VECTOR*
void Vector::toGamestudio(VECTOR * vec) const
{
	vec->x = _VAR(x);
	vec->y = _VAR(y);
	vec->z = _VAR(z);
}

///Normal addition
Vector operator+(Vector const& a, Vector const& b)
{
	return Vector(a.x+b.x, a.y+b.y, a.z+b.z);
}


///Normal subtraction
Vector operator-(Vector const& a, Vector const& b)
{
	return Vector(a.x-b.x, a.y-b.y, a.z-b.z);
}


///Multiply with a scalar
Vector operator*(double k, Vector const& rhs)
{
	return Vector(rhs.x*k, rhs.y*k, rhs.z*k);
}


///Equals
bool operator==(Vector const& a, Vector const& b)
{
	return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
}


///Not equal
bool operator!=(Vector const& a, Vector const& b)
{
	return !((a.x == b.x) && (a.y == b.y) && (a.z == b.z));
}



///Dot product
double dot(Vector const& a, Vector const& b)
{
	return a.x*b.x + a.y*b.y + a.z*b.z;
}


///Cross product
Vector cross(Vector const& a, Vector const& b)
{
	return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); 
}


///Angle between two vectors
double angleBetween(Vector const& a, Vector const& b)
{
	return acos(dot(a, b) / a.r() / b.r());
}



///Print a vector to  a stream
std::ostream& operator<<(std::ostream & os, Vector const& rhs)
{
	os << '(' << rhs.x << " , " <<rhs.y << " , " << rhs.z << ')';

	return os;
}
